﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using DG.Common.Patterns.Commands;
using DG.Common.Patterns.MVVM;

namespace DG.Common.EF.Reflection
{
    public class EntityViewerModel : ViewModelBase
    {
        private CRUDOperationType _crudAction;
        private object _entity;
        private IDbContext _context;
        private EntityViewerProperty[] _entityViewerPropertyList;
        private Type _type;
        private ContextConceptualInfos _conceptualInfos;

        public object Entity
        {
            get { return _entity; }
            private set
            {
                if (Equals(value, _entity)) return;
                _entity = value;
                OnPropertyChanged("Entity");
                OnPropertyChanged("SaveCommand");
            }
        }

        public IDbContext Context
        {
            get { return _context; }
            private set
            {
                if (Equals(value, _context)) return;
                _context = value;
                OnPropertyChanged("Context");
                OnPropertyChanged("SaveCommand");
            }
        }

        public EntityViewerProperty[] EntityViewerPropertyList
        {
            get { return _entityViewerPropertyList; }
            private set
            {
                if (Equals(value, _entityViewerPropertyList)) return;
                _entityViewerPropertyList = value;
                OnPropertyChanged("EntityViewerPropertyList");
            }
        }

        public Type Type
        {
            get { return _type; }
            private set
            {
                if (Equals(value, _type)) return;
                _type = value;
                OnPropertyChanged("Type");
                OnPropertyChanged("SaveCommand");
            }
        }

        public ContextConceptualInfos ConceptualInfos
        {
            get { return _conceptualInfos; }
            private set
            {
                if (Equals(value, _conceptualInfos)) return;
                _conceptualInfos = value;
                OnPropertyChanged("ConceptualInfos");
            }
        }

        public CRUDOperationType CRUDAction
        {
            get { return _crudAction; }
            private set
            {
                if (value == _crudAction) return;
                _crudAction = value;
                OnPropertyChanged("CRUDAction");
                OnPropertyChanged("SaveCommand");
            }
        }

        public ICommand SaveCommand
        {
            get
            {
                return new FunctionalCommand(() =>
                {
                    if (CRUDAction == CRUDOperationType.Create)
                    {
                        Context.Set(Type).Add(Entity);
                    }

                    if (Context.Context.GetValidationErrors().Any())
                    {
                        var msg = new StringBuilder();
                        msg.AppendLine("Alguns Erros Foram Detectados");
                        foreach (var entry in Context.Context.GetValidationErrors().GroupBy(e => e.Entry))
                            foreach (var erro in entry.SelectMany(e => e.ValidationErrors))
                            {
                                msg.AppendLine("Entidade: " + entry.Key.Entity +
                                               ", Propriedade: " + erro.PropertyName +
                                               ", Erro: " + erro.ErrorMessage).AppendLine();
                            }
                        SendMessage(msg.ToString());
                        return;
                    }
                    
                    Context.SaveChanges();

                    SendMessage("Entidade [" + Type.Name +"] cadastrada com sucesso!");
                    RaiseFinished();

                }, () => Entity != null);
            }
        }
        
        public ICommand CancelCommand
        {
            get
            {
                return new FunctionalCommand(RaiseFinished);
            }
        }

        public EntityViewerModel(IDbContext ctx, Type entityType, CRUDOperationType crudAction, object[] entityKeys = null,
            IEnumerable<PropertyInfo> readOnlyProperties = null,
            IEnumerable<PropertyInfo> hideProperties = null)
        {
            CRUDAction = crudAction;
            if (CRUDAction == CRUDOperationType.Create && entityKeys != null)
                throw new InvalidOperationException("Construtor com chave não pode ser chamado no modo CRUDAction.Create");
            if (CRUDAction != CRUDOperationType.Create && entityKeys == null)
                throw new InvalidOperationException("Construtor sem chave só pode ser chamado no modo CRUDAction.Create");

            Context = ctx;
            ConceptualInfos = ContextConceptualInfosFactory.GetContextEdmxForContext(ctx.Context);
            Type = entityType;

            Entity = crudAction == CRUDOperationType.Create
                ? Entity = Activator.CreateInstance(entityType) 
                : Context.Set(entityType).Find(entityKeys);
            
            var propertiesMeta = ConceptualInfos.EntitiesConceptualInfos[entityType].PropertiesMeta;

            var hashHideProperties = new HashSet<string>((hideProperties ?? propertiesMeta.Values.Where(p => p.IsNavigationCollection).Select(p=> p.Property))
                .Select(p => p.Name));
            var hashreadOnlyProperties = new HashSet<string>((readOnlyProperties ?? new List<PropertyInfo>()).Select(p => p.Name));


            EntityViewerPropertyList = propertiesMeta.Where(p => !hashHideProperties.Contains(p.Value.Property.Name))
                .Select(p => new EntityViewerProperty(this, p.Value, 
                    hashreadOnlyProperties.Contains(p.Value.Property.Name) ? true : (bool?) null))
                    .Where(p=> !(p.EntityPropertyConceptualInfo.IsKey && p.EntityPropertyConceptualInfo.IsAutoIncrement))
                    .ToArray();

            if (CRUDAction == CRUDOperationType.Create)
            {
                foreach (var property in EntityViewerPropertyList
                    .Where(p=> p.EntityPropertyConceptualInfo.Property.PropertyType == typeof(DateTime)))
                {
                    property.Value = DateTime.Now.Date;
                }
            }
        }

    }
    
}